---
title: java经典框架-springboot (续)
date: 2024-03-22 22:10:43
images: ['https://kodo.huixiangwuyou.com/blog/images/345687881215.jpeg']
summary: 'SpringBoot 本章学习内容： Redis、 任务、 分布式、 swagger、 项目打包与部署'
tags: 
 - java
---


## Redis

说明:在 SpringBoot2.x之后，原来使用的jedis 被替换为了 lettuce?
- jedis:采用的査连，多个线程操作的话，是不安全的，如果想要避免不安全的，使用jedis pool 连接池! 更像 B0 模式
- lettuce:采用netty，实例可以再多个线程中进行共享，不存在线程不安全的情况!可以减少线程数据了，更像 Nio 横式

### 基本使用
```java  
// 1. 添加依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

// 2. 配置 Redis 连接信息
spring.redis.host=127.0.0.1
spring.redis.port=6379

// 3. 使用 RedisTemplate 操作 Redis
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class RedisController {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @GetMapping("/redis")
    public String redis() {
        redisTemplate.opsForValue().set("key", "value");
        Object value = redisTemplate.opsForValue().get("key");
        return value.toString();
    }
}
```
> RedisTemplate 是 Spring Boot 提供的一个用于操作 Redis 的模板类，它封装了 Redis 的各种操作，可以方便地进行数据的读写操作。但是在实际开发中往往不太适合我们企业级自定义的开发，所以一般我们都是自定义 RedisTemplate 的配置类的序列化，以便在本地redis-cli可以直接查看而不是查看转译的。

```java 
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);

        // 设置键的序列化方式（String）
        StringRedisSerializer stringSerializer = new StringRedisSerializer();
        template.setKeySerializer(stringSerializer);
        template.setHashKeySerializer(stringSerializer);

        // 设置值的序列化方式（JSON）
        GenericJackson2JsonRedisSerializer jsonSerializer = new GenericJackson2JsonRedisSerializer();
        template.setValueSerializer(jsonSerializer);
        template.setHashValueSerializer(jsonSerializer);

        // 设置默认序列化方式（可选）
        template.setDefaultSerializer(jsonSerializer);

        // 初始化 RedisTemplate 的其他配置
        template.afterPropertiesSet();

        return template;
    }
}
```
### redis工具类

```java >folded
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

@Component
public class RedisUtil {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    // ==================== String 相关操作 ====================

    /**
     * 设置键值对（默认永不过期）
     */
    public void set(String key, Object value) {
        redisTemplate.opsForValue().set(key, value);
    }

    /**
     * 设置键值对，并设置过期时间
     */
    public void set(String key, Object value, long timeout, TimeUnit unit) {
        redisTemplate.opsForValue().set(key, value, timeout, unit);
    }

    /**
     * 获取键对应的值
     */
    public Object get(String key) {
        return redisTemplate.opsForValue().get(key);
    }

    // ==================== Hash 相关操作 ====================

    /**
     * 设置 Hash 字段
     */
    public void hSet(String key, String field, Object value) {
        redisTemplate.opsForHash().put(key, field, value);
    }

    /**
     * 获取 Hash 字段值
     */
    public Object hGet(String key, String field) {
        return redisTemplate.opsForHash().get(key, field);
    }

    /**
     * 获取整个 Hash
     */
    public Object hGetAll(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    // ==================== List 相关操作 ====================

    /**
     * 左端插入 List
     */
    public void lPush(String key, Object value) {
        redisTemplate.opsForList().leftPush(key, value);
    }

    /**
     * 右端插入 List
     */
    public void rPush(String key, Object value) {
        redisTemplate.opsForList().rightPush(key, value);
    }

    /**
     * 获取 List 范围内的元素
     */
    public Object lRange(String key, long start, long end) {
        return redisTemplate.opsForList().range(key, start, end);
    }

    // ==================== Set 相关操作 ====================

    /**
     * 添加 Set 元素
     */
    public void sAdd(String key, Object value) {
        redisTemplate.opsForSet().add(key, value);
    }

    /**
     * 获取 Set 所有元素
     */
    public Object sMembers(String key) {
        return redisTemplate.opsForSet().members(key);
    }

    // ==================== ZSet 相关操作 ====================

    /**
     * 添加 ZSet 元素（带分数）
     */
    public void zAdd(String key, double score, Object value) {
        redisTemplate.opsForZSet().add(key, value, score);
    }

    /**
     * 获取 ZSet 范围内的元素（按分数升序）
     */
    public Object zRange(String key, long start, long end) {
        return redisTemplate.opsForZSet().range(key, start, end);
    }

    // ==================== 通用操作 ====================

    /**
     * 删除键
     */
    public boolean delete(String key) {
        return Boolean.TRUE.equals(redisTemplate.delete(key));
    }

    /**
     * 判断键是否存在
     */
    public boolean hasKey(String key) {
        return Boolean.TRUE.equals(redisTemplate.hasKey(key));
    }

    /**
     * 设置过期时间
     */
    public boolean expire(String key, long timeout, TimeUnit unit) {
        return Boolean.TRUE.equals(redisTemplate.expire(key, timeout, unit));
    }

    /**
     * 获取剩余过期时间
     */
    public long getExpire(String key) {
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }

    /**
     * 清空所有 Redis 数据（慎用！）
     */
    public void flushDB() {
        redisTemplate.execute((connection) -> {
            connection.flushDb();
            return null;
        });
    }
}
```

<br/>

## 任务
### 异步任务
在执行一些不需要立即返回结果的任务时，可以使用异步任务来提高系统的并发性能。Spring Boot 提供了异步任务的支持，可以通过以下步骤实现异步任务：
```java >folded
// 1. 在启动类上添加 @EnableAsync 注解开启异步任务支持
@SpringBootApplication
@EnableAsync
public class SpringbootApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringbootApplication.class, args);
    }
}

// 2. 在需要异步执行的方法上添加 @Async 注解
@Service
public class AsyncService {
    @Async
    public void asyncMethod() {
         try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("test");
    }
  
}

// 3. 调用异步方法
@RestController
public class AsyncController {
    @Autowired
    private AsyncService asyncService;

    @GetMapping("/async")
    public String async() {
        asyncService.asyncMethod();
        return "success";
    }
}
```

### 定时任务
[cron 表达式](https://next.huixiangwuyou.com/blog/Jenkins/useJenkins)或者[标准 cron](https://crontab.guru)
```java >folded
// 1. 在启动类上添加 @EnableScheduling 注解开启定时任务支持
@SpringBootApplication
@EnableScheduling
public class SpringbootApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringbootApplication.class, args);
    }
}

// 2. 在需要定时执行的方法上添加 @Scheduled 注解
@Service
public class ScheduledService {
    @Scheduled(cron = "0/5 * * * * ?") // 每5秒执行一次    0 */5 * * * ? 每5分钟执行一次
    // @Scheduled(cron = "0 * 19 * * ?")  每分钟执行一次   0 15 10 ? *1-6 每个月的周一到周六 10.15分钟执行一次
    public void scheduledMethod() {
        System.out.println("定时任务执行");
    }
}
```


### 邮件任务
```java >folded
// 1. 在 application.properties 中配置邮件服务器信息
spring.mail.host=smtp.example.com  // SMTP 服务器地址  smtp.qq.com
spring.mail.username=your-email@example.com
spring.mail.password=your-email-password // QQ邮箱授权码
spring.mail.properties.mail.smtp.ssl.enable=true

// 2. 创建邮件发送器
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.mail.javamail.MimeMessageHelper;

@Service
public class EmailService {
    @Autowired
    private JavaMailSender mailSender;

    public void sendEmail() {
        SimpleMailMessage mailMessage = new SimpleMailMessage();
        mailMessage.setFrom("515072052@qq.com"); // 确保发件人地址与配置文件
        mailMessage.setSubject("测试邮件");
        mailMessage.setText("测试邮件内容");
        mailMessage.setTo("515072052@qq.com");
        mailSender.send(mailMessage);
    } 
    // 复杂邮件发送
    public void sendComplexEmail() {
        MimeMessage message = mailSender.createMimeMessage();
        // 组装
        MimeMessageHelper helper = new MimeMessageHelper(message, true);
        // 正文
        helper.setText("<html><head></head><body><h1 style='color:red'>你好，这是一个HTML文本</h1></body></html>", true);
        helper.setSubject("测试邮件");

        // 附件
        helper.addAttachment("test.txt", new java.io.File("C:\\Users\\admin\\Desktop\\服务器常用命令.txt"));
        helper.addAttachment("test.png", new java.io.File("C:\\Users\\admin\\Pictures\\22222222222.jpg"));

        helper.setFrom("515072052@qq.com");
        helper.setTo("8xxxx48@qq.com");
    }

}

// 3. 调用邮件发送器发送邮件
@RestController
public class EmailController {
    @Autowired
    private EmailService emailService;

    @GetMapping("/sendEmail")
    public String sendEmail() {
        emailService.sendComplexEmail();
        return "success";
    }
}
```
> 注意 ：QQ邮箱需要开启 SMTP 服务，并获取授权码作为密码，而不是邮箱密码。application中的username看情况定，可能有几个邮箱别名，要使用当前授权码的邮箱别名。

![自定义邮件发送](https://kodo.huixiangwuyou.com/blog/images/java/650427175900.png)


## 分布式

分布式系统由多个独立计算机（节点）组成，通过网络协同工作，对外表现为一个整体系统。每个节点可以独立部署、扩展和维护。
> 单一系统、分布式、云服务
| 特性 | 单一系统 | 分布式系统 | 	云开发 |
| --- | --- | --- | --- |
| 架构 | 单体 | 微服务/分布式 | 基于云服务（IaaS/PaaS/Serverless） |
| ​扩展性 | 差（垂直扩展） | 高（水平扩展） | 极高（自动扩缩容） |
| ​运维复杂度 | 低 | 高 | 低（云平台托管） |
| ​成本 | 固定（硬件投入） | 较高（需管理多节点） | 按需付费（灵活控制） |
| ​适用场景 | 小型应用 | 大型企业级系统 | 快速开发、初创公司 |
|  |  |  |  |

> 未来趋势

- 云原生（Cloud Native）​：微服务 + 容器化（Docker/K8s） + Serverless。
- 边缘计算（Edge Computing）​：计算靠近数据源（如 IoT 设备）。
- ​Serverless 进化：更多 BaaS 服务，降低开发门槛。

### RPC                 
<br/>    
RPC【Remote Procedure Call】是指远程过程调用，是一种进程间通信方式，他是一种技术的思想，而不是规范，它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数，而不用程序员显式编码这个远程调用的细节。即程序员无论是调用本地的还是远程的的数，本质上编写的调用代码基本相同。
也就是说两台服务器A，B，一个应用部署在A服务器上，想要调用B服务器上应用提供的函数的方法，由于不在一个内存空间，不能直接调用，需要通过网络来表达调用的语义和传达调用的数据。为什么要用RPC呢?就是无法在一个进程内，甚至一个计算机内通过本地调用的方式完成的需求，比如不同的系统间的通讯，甚至不同的组织间的通讯，由于计算能力需要横向扩展，需要在多台机器组成的集群上部署应用，RPC就是要像调用本地的函数一样去调远程函数;

逻辑流程：
- 将服务器B上的函数注册到服务器A上，作为一个provider。
- 客户端H在服务器A上调用函数，就像调用本地函数一样，实际上是通过网络调用服务器B上的函数，服务器A上有一个proxy，负责接收客户端的请求，并将请求转发给服务器B上的provider给客户端H消费。

![](https://cn.dubbo.apache.org/imgs/v3/quickstart/nacos-kubernetes-deployment.png)
常见框架：
- gRPC(微服务、跨语言推荐)：基于 HTTP/2 和 Protocol Buffers，支持多种语言。
- Thrift：由 Facebook 开发，支持多种语言，使用 IDL 定义接口。
- Dubbo(推荐)：阿里巴巴开源的 RPC 框架，支持多种协议和负载均衡策略的Java微服务。
- Spring Feign(spring cloud推荐)：基于 HTTP 的 RPC 框架，支持 Spring Cloud。

### Dubbo
[Dubbo](https://cn.dubbo.apache.org/zh-cn/overview/home/) 是阿里巴巴开源的 RPC 框架，支持多种协议和负载均衡策略的Java微服务。Dubbo 提供了服务治理、服务发现、负载均衡等功能，可以帮助开发者构建高性能、高可用的分布式系统。提供了多种多种语言，如 Java、Go、Node、Python 等支持。

插件 zkclient-spring-boot-starter 和 dubbo-spring-boot-starter 在 spring boot 项目使用时会有坑点，比如日志会有冲突：解决办法，在依赖中使用 exclusions 标签排除 slf4j-log4j12 依赖。

使用方式：
- zookeeper 作为注册中心
- dubbo-admin 作为监控中心,查看哪些服务被注册，哪些服务被调用
- dubbo jar包作为服务提供者

步骤：
- 服务提供者：引入依赖，配置文件，实现接口，启动类添加注解
  -  1. 引入依赖
  ```xml
    <!-- Dubbo Spring Boot Starter -->
    <dependency>
        <groupId>org.apache.dubbo</groupId>
        <artifactId>dubbo-spring-boot-starter</artifactId>
        <version>3.2.0</version>
    </dependency>

    <!-- Zookeeper 客户端 -->
    <dependency>
        <groupId>org.apache.curator</groupId>
        <artifactId>curator-recipes</artifactId>
        <version>5.3.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.curator</groupId>
        <artifactId>curator-framework</artifactId>
        <version>5.3.0</version>
    </dependency>
  ```
  - 2.配置注册中心的地址，以及服务发现名，和要扫描的包
  ```yml
    # application.yml
    spring:
      application:
        name: dubbo-provider-demo

    dubbo:
      application:
        name: dubbo-provider-demo
      protocol:
        name: dubbo
        port: 20880
      registry:
        address: zookeeper://127.0.0.1:2181
      scan:
        base-packages: com.example.provider.service
  ```
  - 3.在想要被注册的服务上面~增加一个注解 @Service  (使用的是dubbo的而不是spring的)
  ```java
    // UserService.java
    public interface UserService {
        String getUserName(Long id);
        List<String> getUserRoles(Long id);
    }

    // UserServiceImpl.java
    import org.apache.dubbo.config.annotation.DubboService;

    @DubboService
    public class UserServiceImpl implements UserService {
        @Override
        public String getUserName(Long id) {
            return "User-" + id;
        }

        @Override
        public List<String> getUserRoles(Long id) {
            return Arrays.asList("admin", "user");
        }
    }
  ```
- 消费者使用
  - 1. 引入依赖
  - 2. 配置注册中心的地址，配置自己的股务名~
  ```yml
    # application.yml
    spring:
      application:
        name: dubbo-consumer-demo

    dubbo:
      application:
        name: dubbo-consumer-demo
      registry:
        address: zookeeper://127.0.0.1:2181
      consumer:
        check: false
  
  ```
  - 3. 在需要使用的地方使用 @Reference 注解，指定要使用的服务名，就可以直接使用这个服务了
  ```java
    // UserServiceConsumer.java
    import org.apache.dubbo.config.annotation.DubboReference;
    import org.springframework.stereotype.Component;

    @Component
    public class UserServiceConsumer {
        
        @DubboReference
        private UserService userService;
        
        public String getUserName(Long id) {
            return userService.getUserName(id);
        }
        
        public List<String> getUserRoles(Long id) {
            return userService.getUserRoles(id);
        }
    }

    // UserController.java
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RestController;

    @RestController
    public class UserController {
        
        private final UserServiceConsumer userServiceConsumer;
        
        public UserController(UserServiceConsumer userServiceConsumer) {
            this.userServiceConsumer = userServiceConsumer;
        }
        
        @GetMapping("/user/{id}/name")
        public String getUserName(@PathVariable Long id) {
            return userServiceConsumer.getUserName(id);
        }
        
        @GetMapping("/user/{id}/roles")
        public List<String> getUserRoles(@PathVariable Long id) {
            return userServiceConsumer.getUserRoles(id);
        }
    }
  ```
这样，消费者就可以通过 Dubbo 框架调用服务提供者的服务了。若是服务提供者有多个实例，Dubbo 会自动进行负载均衡，选择一个最优的实例进行调用。
如果想看可视化监控，可以使用 dubbo-admin。 dubbo-admin支持如下功能：
- 服务治理：查看服务提供者和消费者的信息，查看服务调用的实时数据。
- 服务监控：查看服务调用的实时数据，包括调用次数、调用时间等。
- 服务测试：可以测试服务是否可用，以及服务的性能。
- 服务配置：可以配置服务的参数，修改服务负载均衡的权重。


### Nacos
[Nacos](https://nacos.io/zh-cn/) 是阿里巴巴开源的微服务注册中心，支持服务发现、配置管理、动态 DNS 解析等功能。Nacos 可以帮助开发者构建高性能、高可用的分布式系统。
在上面的例子中，我们使用了 Zookeeper 作为注册中心，现在我们使用 Nacos 作为注册中心。
```xml
<!-- pom.xml -->
<dependency>
    <groupId>com.alibaba.nacos</groupId>
    <artifactId>nacos-client</artifactId>
    <version>2.2.0</version>
</dependency>
```
配置
```yml
# application.yml  provider
spring:
  application:
    name: dubbo-provider-demo

dubbo:
  application:
    name: dubbo-provider-demo
  protocol:
    name: dubbo
    port: 20880
  registry:
    address: nacos://127.0.0.1:8848
    parameters:
      namespace: public
  scan:
    base-packages: com.example.provider.service

# application.yml  consumer
spring:
  application:
    name: dubbo-consumer-demo

dubbo:
  application:
    name: dubbo-consumer-demo
  registry:
    address: nacos://127.0.0.1:8848
    parameters:
      namespace: public
  consumer:
    check: false    
```
接口和实现类不变，只需要修改注册中心的地址即可。
  
<br/>



## Swagger

> Swagger 是一个用于生成API文档的工具，它可以帮助开发人员快速地创建和查看API文档，并且可以与Spring Boot无缝集成。

### 安装依赖
```xml >folded 
  <!-- spring 2.x 版本swagger -->
		<dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-swagger2</artifactId>
		</dependency>
		<dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-swagger-ui</artifactId>
		</dependency>
  <!-- spring 3.x 版本 swagger -->
    <dependency>
      <groupId>org.springdoc</groupId>
      <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
      <version>2.5.0</version> <!-- 使用最新版本 -->
    </dependency>
```

### 配置

```yml >folded 
spring:
  autoconfigure:
    // 若是配置了 security 要关闭，否则要权限
    exclude: org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration

springdoc:
  swagger-ui:
    path: /swagger-ui.html  # Swagger UI 访问路径
    tags-sorter: alpha      # 按字母排序标签
    operations-sorter: alpha
  api-docs:
    path: /v3/api-docs      # OpenAPI JSON 路径
  default-produces-media-type: application/json
  info:
    title: Spring Boot 3 API
    version: 1.0.0
    description: API Documentation
```

> 项目目录下的config文件夹配置 

```java >folded
// spring 2.x 版本swagger
@Configuration
@EnableSwagger2
public class SwaggerConfig {
    @Bean
    public Docket api() {
        return new Docket(DocumentationType.SWAGGER_2)
                .select()
                // 通过 grounpName 配置多个模块分组 要求有多个Docket
                .grounpName("default")
                //为当前包路径 指定扫描的controller
                .apis(RequestHandlerSelectors.basePackage("com.misstree.demo.controller"))
                // 过滤规则
                .paths(PathSelectors.any())
                .build();
    }
    // 配置swagger 信息apiInfo()用来创建该API的基本信息（这些信息会展示在文档页面中）
    public ApiInfo apiInfo() {
      // 作者信息
        Contact contact = new Contact("misstree", "https://github.com/misstree", "misstree@163.com");
        return new ApiInfo(
          title: "Spring Boot 2 API",
          description: "API Documentation",
          version: "1.0.0",
          contact: contact,
          license: new License("Apache 2.0", "http://www.apache.org/licenses/LICENSE-2.0.html"),
          licenseUrl: "http://www.apache.org/licenses/LICENSE-2.0.html",
          new ArrayList()
        )
    }
    }

}

// spring 3.x 版本 swagger
import org.springframework.context.annotation.Profile;

@Configuration
@Profile({"dev", "test"}) // 仅在 dev 和 test 环境下启用,生产环境不启用保证安全和性能
public class SwaggerConfig {
    @Bean
    public OpenApi openApi() {
        return new OpenApi()
        .info(new Info()
        .title("Spring Boot 3 API")
        .version("1.0.0")
        .description("API Documentation")
      );
    }
}
```

### 使用
```java >folded
// spring 2.x 版本swagger
package com.example.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

    @Operation(summary = "获取用户信息", description = "根据用户 ID 获取用户信息")
    @ApiResponses(value = {
        @ApiResponse(responseCode = "200", description = "成功获取用户信息"),
        @ApiResponse(responseCode = "404", description = "用户未找到")
    })
    @GetMapping("/users/{id}")
    public String getUser(@PathVariable @Parameter(description = "用户 ID") Long id) {
        return "User with id: " + id;
    }
}
// spring 3.x 版本swagger
package com.misstree.demo.controller;

import com.misstree.demo.commentsMaper.Comments;
import com.misstree.demo.mapper.CommentsMaper;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@Data
@AllArgsConstructor
@NoArgsConstructor
@RestController
// 功能模块的名称 描述
@Tag(name = "评论模块", description = "用于博客文章的评论管理")
@RequestMapping("/comments") // 接口请求路径前缀
public class CommentsController {

    @Autowired
    private CommentsMaper commentsMaper;

    @GetMapping("/getAllComments")
    // 描述接口的功能
    @Operation(summary = "获取评论列表")
    // 设置 dto 方式请求数据
    @Parameters({
            @Parameter(name = "name", description = "评论人姓名"),
            @Parameter(name = "content", description = "评论内容"),
            @Parameter(name = "time", description = "评论时间"),
    })
    // 设置响应的结果
    @ApiResponses({
            @ApiResponse(responseCode = "200", description = "获取评论列表成功"),
            @ApiResponse(responseCode = "400", description = "参数错误"),
            @ApiResponse(responseCode = "401", description = "没有权限"),
            @ApiResponse(responseCode = "403", description = "禁止访问"),
    })
    public List<Comments> getComments(){
        return commentsMaper.gets();
    }
}

```

### 访问
http://localhost:8080/swagger-ui.html
![swagger](https://kodo.huixiangwuyou.com/blog/images/java/60250426231644.png)


## 打包与部署
在项目开发完成后，需要使用 Maven 或 Gradle 构建工具将项目打包成可执行的 jar 文件，并部署到服务器上运行。

### Maven
[Maven下载](https://maven.apache.org/download.cgi)对应的压缩文件，windows 下载 	apache-maven-3.x.x-bin.zip 文件。将下载好的文件解压到指定目录`D:\apache-maven-3.9.9`，配置环境变量`D:\apache-maven-3.9.9\bin`，在命令行输入 `mvn -v` 查看是否配置成功。
```shell
mvn clean package -Dmaven.test.skip=true
```
> -Dmaven.test.skip=true 跳过测试

这样在`target` 目录下会生成一个可执行的 jar 文件，使用命令 `java -jar xxx.jar` 启动项目。

修改服务配置
- java -jar xxx.jar --server.port=8880
- java -jar xxx.jar --spring.profiles.active=dev
- java -jar xxx.jar --server.port=8880 --spring.profiles.active=dev --spring.datasource.url=jdbc:mysql://localhost:3306/test

项目配置优先级：
> 命令行参数 > 环境变量 > 配置文件 > 默认配置
- 命令行参数：java -jar xxx.jar --server.port=8880
- 环境变量：在启动项目时，通过设置环境变量来传递配置参数，例如：export JAVA_OPTS="-Dserver.port=8880"
- 配置文件：在项目打包好文件`target`层级下设置，例如：application.properties 或 application.yml，本质的等同于之前说的 配置文件优先级的设置
- 默认配置：在 Spring Boot 的默认配置resources/application.properties 中设置参数

